package com.beidasoft.web.util;

import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;


/**
 * version:1.0
 * 主要为了解决部分位置并发使用的问题
 * @date 2021.11.02
 */
public class UidGeneratorUtils {

    /**
     * 毫秒内序列(0-4095)
     */
    private static long sequence = 0L;

    /**
     * 上次生成 ID 的时间截
     */
    private static long lastTimestamp = -1L;

    private static String lastUUID = "ea64aa55-aefb-4b22-8682-fb7c186fa5cf";

    /**
     * 获得下一个ID (该方法是线程安全的)
     *
     * @return SnowflakeId
     */
     public static synchronized String nextUid() {
        long timestamp = timeGen();

        // 如果当前时间小于上一次ID生成的时间戳，说明系统时钟回退过这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(
                    String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }

        // 如果是同一时间生成的，则进行毫秒内序列
        if (lastTimestamp == timestamp) {
            // 阻塞到下一个毫秒,获得新的时间戳
            timestamp = tilNextMillis(lastTimestamp);
        } else {
            // 时间戳改变，毫秒内序列重置
            // 在跨毫秒时，序列号总是归0，会使得序列号为0的ID比较多，导致生成的ID取模后不均匀。
            // 解决方法是，序列号不是每次都归0，而是归一个0到9的随机数
            sequence = ThreadLocalRandom.current().nextLong(10);
        }

        // 上次生成ID的时间截
        lastTimestamp = timestamp;

        // 移位并通过或运算拼到一起组成64位的ID
        String nextId = UUID.randomUUID().toString();

        return nextId;
    }

    /**
     * 阻塞到下一个毫秒，直到获得新的时间戳
     *
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    private static long tilNextMillis(final long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     *
     * @return 当前时间(毫秒)
     */
    private static long timeGen() {
        return System.currentTimeMillis();
    }
}
